/*
 * Copyright (C) Jahia Ltd. All rights reserved.
 *
 * This software is published under the terms of the Jahia Open Software
 * License version 1.1, a copy of which has been included with this
 * distribution in the LICENSE.txt file.
 */
package org.jahia.sqlprofiler.gui;

import javax.swing.table.*;
import java.util.*;
import org.jahia.sqlprofiler.QueryEntry;
import java.text.DateFormat;
import javax.swing.JTable;
import java.awt.event.*;
import javax.swing.*;
import org.jahia.sqlprofiler.QueryStatistics;
import java.io.StringWriter;
import java.io.PrintWriter;
import org.jahia.sqlprofiler.QueryStatEntry;
import java.text.NumberFormat;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;

/**
 * <p>Title: SQL Profiler</p>
 * <p>Description: </p>
 * <p>Copyright: Copyright (c) 2003</p>
 * <p>Company: Jahia Ltd</p>
 * @author Serge Huber
 * @author Jean-Philippe Valentin
 * @version 1.0
 *
 * 11/14/2003 : Add save table profile statistics as report file CSV by Jean-Philippe VALENTIN (JPV)
 */

public class ProfileStatementTableModel extends AbstractTableModel
    implements Comparator {

    private QueryStatistics queryStats = new QueryStatistics();

    private static final org.apache.log4j.Logger logger = org.apache.log4j.
        Logger.getLogger(ProfileStatementTableModel.class);

    private LoggerTableModel loggerModel;
    private JLabel statementCountValueLabel = null;

    private ArrayList queryEntries = new ArrayList();
    private SortedSet sortedQueryEntries = new TreeSet();
    protected int currCol = 0;
    protected Vector ascendCol = new Vector(); // this vector stores the state (ascending or descending) of each column
    protected Integer one = new Integer(1);
    protected Integer minusOne = new Integer( -1);

    private long lowestQueryTime = Long.MAX_VALUE;
    private long highestQueryTime = Long.MIN_VALUE;
    private long totalQueryTime = 0;

    //  String logEntry = now + "|"+ elapsed + "|"+(connectionId==-1 ? "" : String.valueOf(connectionId))+"|"+category+"|"+prepared+"|"+sql;
    private static final String[] COLUMN_NAMES = {
        "Date", "Time[ms]", "ID", "Category", "Prepared", "SQL"};

    private static final SimpleDateFormat DATE_FORMATTER = new SimpleDateFormat ("yyyy.MM.dd hh:mm:ss.SSS");
    private javax.swing.JLabel statementTimeValueLabel;
    private ProfileResultTableModel profileResultModel;

    public ProfileStatementTableModel(LoggerTableModel loggerModel) {
        this.loggerModel = loggerModel;
        for (int i = 0; i < COLUMN_NAMES.length; i++) {
            if (i == 0) {
                ascendCol.add(minusOne);
            } else {
                ascendCol.add(one);
            }
        }
    }

    public void processP6Event(String eventText) {
        try {
            QueryEntry queryEntry = new QueryEntry(eventText);
            addQuery(queryEntry);
            queryStats.processSQL(queryEntry);
        } catch (NoSuchElementException nsee) {
            logger.error("Error while parsing p6spy format", nsee);
        }
    }

    public void displayOccurenceStats(boolean displayQueries) {
        StringWriter strWriter = new StringWriter();
        PrintWriter ptrWriter = new PrintWriter(strWriter);

        profileResultModel.clear();

        Set sortedQueryStats = queryStats.getQueryStatsByOccurence();

        Iterator queryStatByOccurenceIter = queryStats.getQueryStatsByOccurence().
            iterator();
        while (queryStatByOccurenceIter.hasNext()) {
            QueryStatEntry curQueryStat = (QueryStatEntry)
                queryStatByOccurenceIter.next();
            double curPercentage = 0.0;
            if (queryStats.getTotalElapsedQueryTime() > 0) {
                curPercentage = (100.0 * curQueryStat.getTotalElapsedTime()) /
                    queryStats.getTotalElapsedQueryTime();
            } else {
                curPercentage = (100.0 *
                                 curQueryStat.getOccurences()) /
                    queryStats.getOccurenceCount();
            }
            NumberFormat nf = NumberFormat.getInstance();
            nf.setMaximumFractionDigits(2);
            ProfileReportResult profileResult = new ProfileReportResult();
            profileResult.setPercentage(curPercentage);
            profileResult.setOccurences(curQueryStat.getOccurences());
            profileResult.setTotalElapsedTime(curQueryStat.getTotalElapsedTime());
            profileResult.setTableNames(curQueryStat.getTableNames());
            profileResult.setColumnNames(curQueryStat.getColumnNames());

            profileResultModel.addProfileReportResult(profileResult);
            /*
                         ptrWriter.println(nf.format(curPercentage) +
                              "% Occurences=" + curQueryStat.getOccurences() +
                 " Total time=" + curQueryStat.getTotalElapsedTime() +
                              " Table(s)=" + curQueryStat.getTableNames() +
                              " Column(s)=" + curQueryStat.getColumnNames() +
                              "<br>");
                         if (displayQueries) {
                Iterator queryIter = curQueryStat.getQueries().iterator();
                while (queryIter.hasNext()) {
                    String curQuery = (String) queryIter.next();
                    ptrWriter.println("    " + curQuery + "<br>");
                }
                         }
             */
        }
        profileResultModel.fireTableDataChanged();
    }

    public void saveSQLIndexFile(String indexesFileName) {
        logger.debug("Writing indexes SQL to file [" + indexesFileName +
                     "]...");
        try {
            FileWriter fileWriter = new FileWriter(indexesFileName);
            PrintWriter writer = new PrintWriter(fileWriter);
            Map generatedIndexes = queryStats.getGeneratedIndexes();
            Iterator indexNameIter = generatedIndexes.
                keySet().iterator();
            while (indexNameIter.hasNext()) {
                String curIndexName = (String) indexNameIter.next();
                String indexSql = (String) generatedIndexes.
                    get(curIndexName);
                writer.println(indexSql);
            }
            writer.flush();
            writer.close();
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }
        logger.debug("done.");

    }

//JPV : method called on the button to save SQL table profile statistics generated in a file
    public void saveReportFile(String reportFileName) {
        logger.debug("Writing report to file [" + reportFileName +
                     "]...");
        try {
            FileWriter fileWriter = new FileWriter(reportFileName);
            PrintWriter writer = new PrintWriter(fileWriter);
           
             Set sortedQueryStats = queryStats.getQueryStatsByOccurence();

             Iterator queryStatByOccurenceIter = queryStats.getQueryStatsByOccurence().iterator();

             while (queryStatByOccurenceIter.hasNext()) {
                QueryStatEntry curQueryStat = (QueryStatEntry) queryStatByOccurenceIter.next();
                double curPercentage = 0.0;
                if (queryStats.getTotalElapsedQueryTime() > 0) {
                    curPercentage = (100.0 * curQueryStat.getTotalElapsedTime()) /
                    queryStats.getTotalElapsedQueryTime();
                } else {
                    curPercentage = (100.0 *
                                 curQueryStat.getOccurences()) /
                                 queryStats.getOccurenceCount();
                }
                NumberFormat nf = NumberFormat.getInstance();
                nf.setMaximumFractionDigits(2);

                //Find throws SQL Statement Attach to curQueryStat
                Set setQueries = curQueryStat.getQueries();
                Iterator setQueriesIter = setQueries.iterator();
                QueryEntry querySQL = (QueryEntry) setQueriesIter.next();

                //First get prepared statement or Sql Statement if null
                String strQuerySQL = querySQL.getPreparedSQL();
                if (strQuerySQL == null)
                { strQuerySQL = querySQL.getSqlStatement(); }
/*
                while (setQueriesIter.hasNext()) {
                    QueryEntry querySQL = (QueryEntry) setQueriesIter.next();
                    writer.println(querySQL.getSqlStatement());
                }
*/
                writer.println(curPercentage + "," + 
                               curQueryStat.getTotalElapsedTime() + "," + 
                               curQueryStat.getOccurences() + ",\"" +
                               curQueryStat.getTableNames() + "\",\"" +
                               curQueryStat.getColumnNames() + "\",\"" +
                               strQuerySQL + "\""
                              );
             }
            writer.flush();
            writer.close();
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }
        logger.debug("done.");

    }
//End JPV
    
    public int getRowCount() {
        return queryEntries.size();
    }

    public int getColumnCount() {
        return COLUMN_NAMES.length;
    }

    public String getColumnName(int aCol) {
        // does not need to be synchronized
        return COLUMN_NAMES[aCol];
    }

    public void addQuery(QueryEntry queryEntry) {
        queryEntry.setReceptionRank(queryEntries.size());
        queryEntries.add(queryEntry);
        sortedQueryEntries.add(queryEntry);
        long entryTime = queryEntry.getTime();
        if (entryTime > highestQueryTime) {
            highestQueryTime = entryTime;
        }
        if (entryTime < lowestQueryTime) {
            lowestQueryTime = entryTime;
        }
        if (queryEntry.getElapsedTime() > 0) {
            totalQueryTime += queryEntry.getElapsedTime();
        }
    }

    public void updateQueryStatsDisplay() {
        if (statementCountValueLabel != null) {
            statementCountValueLabel.setText(Integer.toString(queryEntries.size()));
        }
        if (statementTimeValueLabel != null) {
            statementTimeValueLabel.setText(Long.toString(totalQueryTime));
        }
    }

    public void clear() {
        queryEntries.clear();
        sortedQueryEntries.clear();
        lowestQueryTime = Long.MAX_VALUE;
        highestQueryTime = Long.MIN_VALUE;
        totalQueryTime = 0;
        currCol = 0;
        if (statementCountValueLabel != null) {
            statementCountValueLabel.setText(Integer.toString(queryEntries.size()));
        }
        if (statementTimeValueLabel != null) {
            statementTimeValueLabel.setText("0");
        }
        queryStats.clear();
    }

    public Object getValueAt(int rowIndex, int columnIndex) {

        QueryEntry curQueryEntry = (QueryEntry) queryEntries.get(rowIndex);
        return getFormattedQueryEntryColumn(curQueryEntry, columnIndex);
    }

    private Object getFormattedQueryEntryColumn(QueryEntry curQueryEntry,
                                       int columnIndex) {
        Object result = null;
        switch (columnIndex) {
            case 0:
                result = DATE_FORMATTER.format(new Date(curQueryEntry.getTime()));
                break;
            case 1:
                result = new Long(curQueryEntry.getElapsedTime());
                break;
            case 2:
                result = curQueryEntry.getConnectionID();
                break;
            case 3:
                result = curQueryEntry.getCategory();
                break;
            case 4:
                result = curQueryEntry.getPreparedSQL();
                break;
            case 5:
                result = curQueryEntry.getSqlStatement();
                break;

        }
        return result;
    }

    private Object getRawQueryEntryColumn(QueryEntry curQueryEntry,
                                       int columnIndex) {
        Object result = null;
        switch (columnIndex) {
            case 0:
                result = new Date(curQueryEntry.getTime());
                break;
            case 1:
                result = new Long(curQueryEntry.getElapsedTime());
                break;
            case 2:
                result = curQueryEntry.getConnectionID();
                break;
            case 3:
                result = curQueryEntry.getCategory();
                break;
            case 4:
                result = curQueryEntry.getPreparedSQL();
                break;
            case 5:
                result = curQueryEntry.getSqlStatement();
                break;

        }
        return result;
    }

    /*
     * This method is the implementation of the Comparator interface.
     * It is used for sorting the rows
     */
    public int compare(Object v1, Object v2) {

        // the comparison is between 2 vectors, each representing a row
        // the comparison is done between 2 objects from the different rows that are in the column that is being sorted

        int ascending = ( (Integer) ascendCol.get(currCol)).intValue();
        if (v1 == null && v2 == null) {
            return 0;
        } else if (v2 == null) { // Define null less than everything.
            return 1 * ascending;
        } else if (v1 == null) {
            return -1 * ascending;
        }

        QueryEntry left = (QueryEntry) v1;
        QueryEntry right = (QueryEntry) v2;

        Object o1 = getRawQueryEntryColumn(left, currCol);
        Object o2 = getRawQueryEntryColumn(right, currCol);

        // If both values are null, return 0.
        if (o1 == null && o2 == null) {
            return 0;
        } else if (o2 == null) { // Define null less than everything.
            return 1 * ascending;
        } else if (o1 == null) {
            return -1 * ascending;
        }

        if (o1 instanceof Number && o2 instanceof Number) {
            Number n1 = (Number) o1;
            double d1 = n1.doubleValue();
            Number n2 = (Number) o2;
            double d2 = n2.doubleValue();

            if (d1 == d2) {
                return 0;
            } else if (d1 > d2) {
                return 1 * ascending;
            } else {
                return -1 * ascending;
            }

        } else if (o1 instanceof Boolean && o2 instanceof Boolean) {
            Boolean bool1 = (Boolean) o1;
            boolean b1 = bool1.booleanValue();
            Boolean bool2 = (Boolean) o2;
            boolean b2 = bool2.booleanValue();

            if (b1 == b2) {
                return 0;
            } else if (b1) {
                return 1 * ascending;
            } else {
                return -1 * ascending;
            }

        } else if (o1 instanceof Date && o2 instanceof Date) {

            Date date1 = (Date) o1;
            Date date2 = (Date) o2;
            return date1.compareTo(date2) * ascending;

        } else {
            // default case
            if (o1 instanceof Comparable && o2 instanceof Comparable) {
                Comparable c1 = (Comparable) o1;
                Comparable c2 = (Comparable) o2; // superflous cast, no need for it!

                try {
                    return c1.compareTo(c2) * ascending;
                } catch (ClassCastException cce) {
                    // forget it... we'll deal with them like 2 normal objects below.
                }
            }

            String s1 = o1.toString();
            String s2 = o2.toString();
            return s1.compareTo(s2) * ascending;
        }
    }

    /*
     * This method sorts the rows using Java's Collections class.
     * After sorting, it changes the state of the column -
         * if the column was ascending, its new state is descending, and vice versa.
     */
    public void sort() {
        Integer val = (Integer) ascendCol.get(currCol);
        ascendCol.remove(currCol);
        if (val.equals(one)) // change the state of the column
            ascendCol.add(currCol, minusOne);
        else
            ascendCol.add(currCol, one);
        Collections.sort(queryEntries, this);
    }

    public void sortByColumn(int column) {
        this.currCol = column;
        sort();
        fireTableDataChanged();
    }

    // Add a mouse listener to the Table to trigger a table sort
    // when a column heading is clicked in the JTable.
    public void addMouseListenerToHeaderInTable(JTable table) {
        final ProfileStatementTableModel sorter = this;
        final JTable tableView = table;
        tableView.setColumnSelectionAllowed(false);
        MouseAdapter listMouseListener = new MouseAdapter() {
            public void mouseClicked(MouseEvent e) {
                TableColumnModel columnModel = tableView.getColumnModel();
                int viewColumn = columnModel.getColumnIndexAtX(e.getX());
                int column = tableView.convertColumnIndexToModel(viewColumn);
                if (e.getClickCount() == 1 && column != -1) {
                    int shiftPressed = e.getModifiers() & InputEvent.SHIFT_MASK;
                    boolean ascending = (shiftPressed == 0);
                    sorter.sortByColumn(column);
                }
            }
        };
        JTableHeader th = tableView.getTableHeader();
        th.addMouseListener(listMouseListener);
    }

    public void sortAndUpdateTable() {
        Collections.sort(queryEntries, this);
        fireTableDataChanged();
    }

    public JLabel getStatementCountValueLabel() {
        return statementCountValueLabel;
    }

    public QueryEntry getStatementDetails(int rowIndex) {
        return (QueryEntry) queryEntries.get(rowIndex);
    }

    public void setStatementCountValueLabel(JLabel statementCountValueLabel) {
        this.statementCountValueLabel = statementCountValueLabel;
    }

    public javax.swing.JLabel getStatementTimeValueLabel() {
        return statementTimeValueLabel;
    }

    public void setStatementTimeValueLabel(javax.swing.JLabel
                                           statementTimeValueLabel) {
        this.statementTimeValueLabel = statementTimeValueLabel;
    }

    public ProfileResultTableModel getProfileResultModel() {
        return profileResultModel;
    }

    public void setProfileResultModel(ProfileResultTableModel
                                      profileResultModel) {
        this.profileResultModel = profileResultModel;
    }

    public long getLowestQueryTime() {
        return lowestQueryTime;
    }

    public long getHighestQueryTime() {
        return highestQueryTime;
    }

    public ArrayList getQueriesBetweenTime(long lowTime, long highTime) {
        ArrayList resultList = new ArrayList();
        for (int i=0; i < queryEntries.size(); i++) {
            QueryEntry curEntry = (QueryEntry) queryEntries.get(i);
            long entryTime = curEntry.getTime();
            if ( (entryTime >= lowTime) && (entryTime <= highTime)) {
                resultList.add(curEntry);
            }
        }
        return resultList;
    }

    public ArrayList getQueryEntries() {
        return queryEntries;
    }

    public SortedSet getSortedQueryEntries() {
        return sortedQueryEntries;
    }

}